#include "CameraFollow.h"
#include "GameObject.h"
#include "Scene.h"
#include "Follower.h"
#include "Camera.h"
#include "Terrain.h"
#include "Transformation.h"
#include "Logger.h"
#include "Exception.h"
#include "Helpers.hpp"

using namespace game_engine::library;
using namespace game_engine::components;
using namespace game_engine::utils;

///////////////////////////////////////////////////////////////////////////////
// Constructors and Destructors
///////////////////////////////////////////////////////////////////////////////

CameraFollow::CameraFollow(void)
    : State()
{
}

CameraFollow::CameraFollow(Behaviour *parent)
    : State(parent)
{
}

CameraFollow::~CameraFollow(void)
{
}

///////////////////////////////////////////////////////////////////////////////
// Other public methods
///////////////////////////////////////////////////////////////////////////////

void CameraFollow::onEnter(void) throw(...)
{
    // Set the initial Position of the camera
    Transformation *cameraTransf;
    try
    {
        cameraTransf = dynamic_cast<Transformation*> 
            ((fsm->getObject())->getComponent("Transformation"));
    } catch (...)
    {
        string msg("[CameraFollow::onEnter] transformation component not found");
        Logger::ERR(GAMEENGINE_LOG, msg);
        throw RuntimeException(msg);
    }

    Follower *f = dynamic_cast<Follower*> (fsm);
    Point pos = f->getInitialPosition();
    cameraTransf->setPosition(Point(pos.getX(), pos.getY()));

    // Set the next state
    nextState = this;
}

void CameraFollow::onLoop(const FLOAT32) throw(...)
{
    // Camera position may be only updated if a subject has been defined.
    // Otherwise, the camera remains static
    Follower *f = dynamic_cast<Follower*> (fsm);
    if (f->getSubject() == NULL)
    {
        return;
    }

    // Get the subject's position
    Point pos = f->getSubjectPosition();

    // Calculate new camera position
    updateCameraPosition(pos.getX(), pos.getY());
}

///////////////////////////////////////////////////////////////////////////////
// Private methods
///////////////////////////////////////////////////////////////////////////////

void CameraFollow::updateCameraPosition(const FLOAT32 iniX, const FLOAT32 iniY)
{
    FLOAT32 cameraX = iniX;
	FLOAT32 cameraY = iniY;
    Camera *cam;
    try
    {
        cam = dynamic_cast<Camera*> ((fsm->getObject())->getComponent("Camera"));
    } catch (...)
    {
        string msg("[CameraFollow::onLoop] Camera component not found");
        Logger::ERR(GAMEENGINE_LOG, msg);
        //throw RuntimeException(msg);
    }

    // Check limit values
    Rect view = cam->getView();

    // Obtain object "World" from the Scene
    GameObject *world = NULL;
    try
    {
        world = ((fsm->getObject())->getScene())->getObjectByName("World");
    } catch(...) 
    {
        // Nothing happens here, world keeps NULL
    }

    // Get the dimension of the world
    Size worldDim;
    if (world != NULL)
    {
        Terrain *t;
        try
        {
            t = dynamic_cast<Terrain*> (world->getComponent("Terrain"));
            worldDim = t->getDimensions();
        } catch (...)
        {
            // Force world object reference to NULL
            world = NULL;
        }
    }

    // Check limits
	if (cameraX < (0 + (view.getWidth() * 0.5)))
    {
		cameraX = view.getWidth() * 0.5;
	}
	if (cameraY < (0 + (view.getHeight() * 0.5)))
    {
		cameraY = view.getHeight() * 0.5;
	}
    if (world != NULL)
    {
        if (cameraX > (worldDim.getWidth() - (view.getWidth() * 0.5)))
        {
		    cameraX = worldDim.getWidth() - (view.getWidth() * 0.5);
	    }
	    if (cameraY > worldDim.getHeight() - (view.getHeight() * 0.5)) 
        {
		    cameraY = worldDim.getHeight() - (view.getHeight() * 0.5);
	    }
    }

    // Update camera's current position
    Transformation *cameraTransf;
    try
    {
        cameraTransf = dynamic_cast<Transformation*> 
            ((fsm->getObject())->getComponent("Transformation"));
    } catch (...)
    {
        string msg("Camera component : transformation component not found");
        Logger::ERR(GAMEENGINE_LOG, msg);
        // In case of error, the camera position is not updated
        return;
    }

    Point prevPos = cameraTransf->getPosition();
    Point afterPos(cameraX, cameraY);  
    if (prevPos != afterPos)
    {
        cameraTransf->setPosition(afterPos);
        //Logger::DEBUG(GAMEENGINE_LOG, "CameraFollow::onLoop");
        //Logger::DEBUG(GAMEENGINE_LOG, string("New X position = ") + 
        //    to_string<FLOAT32>(cameraTransf->getXPosition(), dec));
        //Logger::DEBUG(GAMEENGINE_LOG, string("New Y position = ") + 
        //    to_string<FLOAT32>(cameraTransf->getYPosition(), dec));
    }
}
